package hpc.exercices.exercice2;

import static org.jocl.CL.CL_CONTEXT_PLATFORM;
import static org.jocl.CL.CL_DEVICE_TYPE_ALL;
import static org.jocl.CL.CL_MEM_COPY_HOST_PTR;
import static org.jocl.CL.CL_MEM_READ_ONLY;
import static org.jocl.CL.CL_MEM_WRITE_ONLY;
import static org.jocl.CL.CL_TRUE;
import static org.jocl.CL.clBuildProgram;
import static org.jocl.CL.clCreateBuffer;
import static org.jocl.CL.clCreateCommandQueue;
import static org.jocl.CL.clCreateContext;
import static org.jocl.CL.clCreateKernel;
import static org.jocl.CL.clCreateProgramWithSource;
import static org.jocl.CL.clEnqueueNDRangeKernel;
import static org.jocl.CL.clEnqueueReadBuffer;
import static org.jocl.CL.clGetDeviceIDs;
import static org.jocl.CL.clGetPlatformIDs;
import static org.jocl.CL.clReleaseCommandQueue;
import static org.jocl.CL.clReleaseContext;
import static org.jocl.CL.clReleaseKernel;
import static org.jocl.CL.clReleaseMemObject;
import static org.jocl.CL.clReleaseProgram;
import static org.jocl.CL.clSetKernelArg;
import hpc.exercices.Profiler;
import hpc.exercices.Utils;

import java.io.IOException;
import java.nio.charset.Charset;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_command_queue;
import org.jocl.cl_context;
import org.jocl.cl_context_properties;
import org.jocl.cl_device_id;
import org.jocl.cl_event;
import org.jocl.cl_kernel;
import org.jocl.cl_mem;
import org.jocl.cl_platform_id;
import org.jocl.cl_program;
import org.junit.Assert;

/**
 * Exercise 2 - Scan naive, efficient
 * 
 * @author Hinterleitner Michael, Heily Daniel
 *
 */

public class Scan {

	/*
	 * open cl variables
	 */
	private static cl_context context;
	
	private static cl_command_queue commandQueue;
	
	private static cl_program program;
	
	private static cl_kernel kernel;

	// array elements
	private static int elements = 1024;

	//efficient code yes or no
	private static boolean efficient_code = true;

	public static void main(String[] args) {

		// int[] array = { 3, 1, 7, 0, 4, 1, 6, 3 };

		int[] array = Utils.randomArray(elements);

		// ////////////////////////////////////////////////////////////
		// SEQUENTIAL //

		int[] result_seq;

		long startSeqScan = System.currentTimeMillis();
		result_seq = SequentialScan.scan(array);
		long endSeqScan = System.currentTimeMillis();
		System.out.println("Duration for SeqScan: "
				+ (endSeqScan - startSeqScan) + " ms");

		// System.out.println(Arrays.toString(result_seq));
		// ////////////////////////////////////////////////////////////

		// read kernel code from external file
		String programSource = null;
		try {

			if (efficient_code) {
				System.out.println("efficient code is running ...");
				// efficient algorithm works for element count power of 2 2^4 ..
				programSource = Utils.readFile(
						"kernelCode/exercise2_efficient.cl",
						Charset.defaultCharset());
			} else {
				// naive algorithm works
				System.out.println("naive code is running ...");
				programSource = Utils.readFile("kernelCode/exercise2_naive.cl",
						Charset.defaultCharset());
			}
		} catch (IOException e) {
			System.err.println("kernel source code couldn't be read! Message:"
					+ e.getMessage());
		}

		int input[] = array;
		int output[] = new int[array.length];

		Pointer inputPointer = Pointer.to(input);
		Pointer outputPointer = Pointer.to(output);

		final int platformIndex = 0;
		final long deviceType = CL_DEVICE_TYPE_ALL;
		final int deviceIndex = 0;

		// enable exceptions
		CL.setExceptionsEnabled(true);

		// obtain the number of platforms
		int numPlatformsArray[] = new int[1];
		clGetPlatformIDs(0, null, numPlatformsArray);
		int numPlatforms = numPlatformsArray[0];

		// obtain a platform ID
		cl_platform_id platforms[] = new cl_platform_id[numPlatforms];
		clGetPlatformIDs(platforms.length, platforms, null);
		cl_platform_id platform = platforms[platformIndex];

		// initialize the context properties
		cl_context_properties contextProperties = new cl_context_properties();
		contextProperties.addProperty(CL_CONTEXT_PLATFORM, platform);

		// obtain the number of devices for the platform
		int numDevicesArray[] = new int[1];
		clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray);
		int numDevices = numDevicesArray[0];

		// obtain a device ID
		cl_device_id devices[] = new cl_device_id[numDevices];
		clGetDeviceIDs(platform, deviceType, numDevices, devices, null);
		cl_device_id device = devices[deviceIndex];

		// create a context for the selected device
		context = clCreateContext(contextProperties, 1,
				new cl_device_id[] { device }, null, null, null);

		// tried without deprecated method, not successful cl_queue_properties
		/*
		 * cl_queue_properties queueProperties = new cl_queue_properties();
		 * queueProperties.addProperty(CL.CL_QUEUE_PROPERTIES,
		 * CL.CL_QUEUE_PROFILING_ENABLE);
		 * queueProperties.addProperty(CL.CL_QUEUE_PROPERTIES,
		 * CL.CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE);
		 * 
		 * // Create a command-queue for the selected device cl_command_queue
		 * cl_command_queue commandQueue =
		 * CL.clCreateCommandQueueWithProperties(context, device,
		 * queueProperties, null);
		 */

		long properties = 0;
		properties |= CL.CL_QUEUE_PROFILING_ENABLE;
		properties |= CL.CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE;

		// create a command-queue for the selected device cl_command_queue
		commandQueue = clCreateCommandQueue(context, device, properties, null);

		// allocate the memory objects for the input- and output data
		cl_mem memObjects[] = new cl_mem[2];
		memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY
				| CL_MEM_COPY_HOST_PTR, Sizeof.cl_int * input.length,
				inputPointer, null);
		memObjects[1] = clCreateBuffer(context, CL_MEM_WRITE_ONLY
				| CL_MEM_COPY_HOST_PTR, Sizeof.cl_int * output.length,
				outputPointer, null);

		// create the program from the source code
		program = clCreateProgramWithSource(context, 1,
				new String[] { programSource }, null, null);

		// build the program
		clBuildProgram(program, 0, null, null, null, null);

		// create the kernel
		kernel = clCreateKernel(program, "scan", null);

		long globalWorkSize[] = null;
		long localWorkSize[] = null;

		if (efficient_code) {
			
			// half of the threads in contrast to naive (2 items in Progress)
			
			// global worksize (total work items)
			globalWorkSize = new long[] { input.length / 2 };

			// local worksize
			localWorkSize = new long[] { input.length / 2 };
		} else {

			// naive algorithm works
			// global worksize (total work items) long
			globalWorkSize = new long[] { input.length };

			// local worksize
			localWorkSize = new long[] { input.length };

		}

		// setting arguments for kernel
		clSetKernelArg(kernel, 0, Sizeof.cl_mem, Pointer.to(memObjects[0]));
		clSetKernelArg(kernel, 1, Sizeof.cl_mem, Pointer.to(memObjects[1]));
		// temp array in kernel
		// because of ---> variable length array declaration not allowed in
		// OpenCL
		clSetKernelArg(kernel, 2, Sizeof.cl_int * input.length * 2, null);
		clSetKernelArg(kernel, 3, Sizeof.cl_int,
				Pointer.to(new int[] { input.length }));

		cl_event kernel_event = new cl_event();

		// execute the kernel
		clEnqueueNDRangeKernel(commandQueue, kernel, 1, null, globalWorkSize,
				localWorkSize, 0, null, kernel_event);

		// wait for kernel "end" event
		CL.clWaitForEvents(1, new cl_event[] { kernel_event });

		// profiler
		System.out.println("Kernel");
		Profiler.profile(kernel_event,true);

		// read the output data
		clEnqueueReadBuffer(commandQueue, memObjects[1], CL_TRUE, 0,
				output.length * Sizeof.cl_int, outputPointer, 0, null, null);

		// release kernel, program, and memory objects
		clReleaseMemObject(memObjects[0]);
		clReleaseMemObject(memObjects[1]);
		shutdown();

		/*//debug
		 * System.out.println(Arrays.toString(output));
		 * System.out.println(Arrays.toString(result_seq));
		 */

		Assert.assertArrayEquals(result_seq, output);

	}

	private static void shutdown() {
		clReleaseKernel(kernel);
		clReleaseProgram(program);
		clReleaseCommandQueue(commandQueue);
		clReleaseContext(context);
	}
}
